﻿using NAudio.Wave;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Windows.Forms;

namespace AudioLabelerTool
{
    public partial class WaveformRenderControl : UserControl
    {

        public event EventHandler<float> SelectionClick;
        public event EventHandler<float> PreviewClick;

        private float[] waveData = new float[0];

        private float getPeakMax(float second)
        {
            float secondindex = second * samplePerSecond;
            int index = (int)Math.Floor(secondindex);
            int realindex = index * 2 + 0;
            if (realindex < 0 || realindex >= waveData.Length)
            {
                return 0;
            }
            return waveData[realindex];
        }

        private float getPeakMin(float second)
        {
            float secondindex = second * samplePerSecond;
            int index = (int)Math.Floor(secondindex);
            int realindex = index * 2 + 1;
            if (realindex < 0 || realindex >= waveData.Length)
            {
                return 0;
            }
            return waveData[realindex];
        }

        private float pixelPerSecond = 60;

        private float samplePerSecond = 50;

        private float currentSecond = 0;

        private float totalSecond = 0;

        private float selectionStart = 2;

        private float selectionEnd = 4;

        private bool hasSelection = false;

        public void setPixelPerSecond(float pps)
        {
            this.pixelPerSecond = pps;
            if (!InvokeRequired)
            {
                Invalidate();
            }
        }


        private float focusLinePos = -1;

        public void ClearSelection()
        {
            hasSelection = false;
            update();
        }

        public void SetSelection(float begin,float end)
        {
            if(begin >= end)
            {
                throw new ArgumentOutOfRangeException("End should not be smaller than Begin");
            }
            selectionStart = begin;
            selectionEnd = end;
            hasSelection = true;
            update();
        }

        public void SetPlayingPos(float sec)
        {
            if(sec < 0 && focusLinePos >= 0)
            {
                focusLinePos = -1;
                update();
                return;
            }
            focusLinePos = sec;
            update();
        }


        public bool HasSelection { get { return hasSelection; } }

        public float SelectionStart { get { return selectionStart; } }

        public float SelectionEnd { get { return selectionEnd; } }


        public float[] scanForWaveFile(ISampleProvider sampleProvider)
        {
            List<float> peakData = new List<float>();
            if(sampleProvider.WaveFormat.Channels != 1)
            {
                sampleProvider = sampleProvider.ToMono();
            }
            int samplePerCalculate = sampleProvider.WaveFormat.SampleRate / (int)samplePerSecond;
            float[] sampleBuffer = new float[sampleProvider.WaveFormat.SampleRate];
            float peakMax = -1;float peakMin = 1;
            int sampleClock = 0;
            int sampleMaxClock = sampleProvider.WaveFormat.SampleRate;
            int lastSampleTickCount = 0;
            int sampleTickCount = 0;
            while (true)
            {
                int len = sampleProvider.Read(sampleBuffer,0, sampleBuffer.Length);
                if(len <= 0)
                {
                    break;
                }
                for (int i = 0; i < len; i++)
                {
                    peakMax = sampleBuffer[i] > peakMax ? sampleBuffer[i] : peakMax;
                    peakMin = sampleBuffer[i] < peakMin ? sampleBuffer[i] : peakMin;
                    sampleClock++;
                    if(sampleClock >= sampleMaxClock) { sampleClock -= sampleMaxClock; }
                    sampleTickCount = sampleClock * ((int)samplePerSecond) / sampleMaxClock;
                    if(lastSampleTickCount != sampleTickCount)
                    {
                        lastSampleTickCount = sampleTickCount;
                        peakData.Add(peakMax);
                        peakData.Add(peakMin);
                        peakMax = -1;
                        peakMin = 1;
                    }
                }

            }
            peakData.Add(peakMax);
            peakData.Add(peakMin);
            return peakData.ToArray();
        }

        public WaveformRenderControl()
        {
            initPaint();
            InitializeComponent();
            this.MouseWheel += WaveformRenderControl_MouseWheel;
            this.DoubleBuffered = true;
        }

        

        private void WaveformRenderControl_Load(object sender, EventArgs e)
        {
            initRender();
            update();
        }

        private void initRender()
        {
            controlDC?.Dispose();
            controlDC = Graphics.FromHwnd(this.Handle);
            renderGraphics?.Dispose();
            renderDC?.Dispose();
            renderDC = new Bitmap(ClientSize.Width < 1 ? 1 : ClientSize.Width, ClientSize.Height < 1 ? 1 : ClientSize.Height);
            renderGraphics = Graphics.FromImage(renderDC);
            renderGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

        }
        private Graphics controlDC = null;
        private Bitmap renderDC = null;
        private Graphics renderGraphics = null;



        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            if (controlDC == null) { return; }
            doRender(renderGraphics, ClientSize.Width < 1 ? 1 : ClientSize.Width, ClientSize.Height < 1 ? 1 : ClientSize.Height);
            e.Graphics.DrawImageUnscaled(renderDC, ClientRectangle.Left, ClientRectangle.Top);
        }

        protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);
            initRender();
            update();
        }

        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            initRender();
            update();
        }

        public void setWaveform(float[] waveform)
        {
            if(waveform == null)
            {
                waveform = new float[0];
            }
            waveData = waveform;
            totalSecond = (float)waveData.Length / 2f / samplePerSecond;
            update();
        }

        private void update()
        {
            if(controlDC == null) { return; }
            doRender(renderGraphics,ClientSize.Width < 1 ? 1 : ClientSize.Width, ClientSize.Height < 1 ? 1 : ClientSize.Height);
            controlDC.DrawImageUnscaled(renderDC, ClientRectangle.Left, ClientRectangle.Top);
        }

        Pen backgroundGrid = null;
        Pen focusLine = null;
        Brush labelPaint = null;
        Brush waveformFill = null;
        Brush selectionFill = null;
        Brush selectionBorder = null;

        private void initPaint()
        {
            backgroundGrid = Pens.Gray;
            waveformFill = Brushes.Cyan;
            labelPaint = Brushes.LightGray;
            selectionFill = new SolidBrush(Color.FromArgb(96, Color.Cyan));
            selectionBorder = new SolidBrush(Color.White);
            focusLine = Pens.Red;
        }

        private void doRender(Graphics g,float width,float height)
        {
            g.Clear(Color.Black);
            if (currentSecond > totalSecond - ((float)Width / (float)pixelPerSecond))
            {
                currentSecond = totalSecond - ((float)Width / (float)pixelPerSecond);
            }
            if(currentSecond < 0)
            {
                currentSecond = 0;
            }
            drawGrid(g, width, height);
            drawWaveform(g, width, height);
            drawSelectionArea(g, width, height);
            drawLabel(g, width, height);
            float focusLineX = sec2Pos(focusLinePos);
            g.DrawLine(focusLine, focusLineX, 0, focusLineX, height);

            //drawDebug(g, width, height);
            
        }

        private void drawGrid(Graphics g, float width, float height)
        {
            float beginSecond = (float)Math.Floor(currentSecond);
            float endSecond = (float)Math.Ceiling(currentSecond + width / pixelPerSecond);
            g.DrawLine(backgroundGrid, 0, height / 2f, width, height / 2f); 
            float lastPixX = -114514;
            for (float sec = beginSecond; sec < endSecond + 1f;sec += 1f)
            {
                float sx = pixelPerSecond * (sec - currentSecond);
                if (sx - lastPixX > 50)
                {
                    g.DrawLine(backgroundGrid, sx, 0, sx, height);
                    lastPixX = sx;
                }
                
            }
        }

        private void drawWaveform(Graphics g, float width, float height)
        {
            float secondPerTick = 1f / samplePerSecond;
            float beginSecond = (float)(currentSecond * secondPerTick) / secondPerTick;
            float endSecond = beginSecond + width / pixelPerSecond + secondPerTick;

            for (float tick = beginSecond; tick < endSecond; tick+=secondPerTick)
            {
                float sx = (tick - currentSecond) * pixelPerSecond;
                float ex = (tick - currentSecond + secondPerTick) * pixelPerSecond;
                float max = getPeakMax(tick);
                float min = getPeakMin(tick);
                float my = height / 2;
                float sy = my - my * max;
                float ey = my - my * min;
                if(ey > sy)
                {
                    g.FillRectangle(waveformFill, sx - 0.4f, sy, ex-sx + 0.8f, ey-sy);
                }
            }
        }

        private void drawLabel(Graphics g, float width, float height)
        {
            float beginSecond = (float)Math.Floor(currentSecond / 2) * 2;
            float endSecond = (float)Math.Ceiling(currentSecond + width / pixelPerSecond);
            float lastPixX = -114514;
            for (float sec = beginSecond; sec < endSecond + 1f; sec += 1f)
            {
                float sx = pixelPerSecond * (sec - currentSecond);
                if(sx - lastPixX > 50)
                {

                    g.DrawString(secondToString(sec), Font, labelPaint, sx, height - 12);
                    lastPixX = sx;
                }
            }
        }

        private string secondToString(float fsecond)
        {
            int second = (int)fsecond;
            int minute = second / 60;
            second = second % 60;
            string str = "";
            if(minute < 10) { str += "0"; }
            str += minute + ":";
            if(second < 10) { str += "0"; }
            str += second;
            return str;
        }

        private void drawSelectionArea(Graphics g,float width,float height)
        {
            if (hasSelection)
            {
                float sx = (selectionStart - currentSecond) * pixelPerSecond;
                float ex = (selectionEnd - currentSecond) * pixelPerSecond;
                if (ex - sx < 1) { ex = sx + 1; }
                g.FillRectangle(selectionFill, sx, 0, ex - sx, height);
                g.FillRectangle(selectionBorder, sx - 3, 0, 3, height);
                g.FillRectangle(selectionBorder, ex, 0, 3, height);

            }
           
                drawNewSelection(g, width, height);
            
        }


        bool hasNewSelection = false;
        float newSelectionOne = 0f;
        float newSelectionTwo = 0f;
        private void drawNewSelection(Graphics g, float width, float height)
        {
            if (hasNewSelection)
            {
                float sx = sec2Pos(newSelectionOne);
                float ex = sec2Pos(newSelectionTwo);
                if (sx > ex)
                {
                    var temp = ex;
                    ex = sx;
                    sx = temp;
                }
                g.FillRectangle(selectionFill, sx, 0, ex - sx, height);

            }
        }

        private float debugX = -10;
        private float debugY = -10;
        private String debugStr = "";
        private void drawDebug(Graphics g,float width,float height)
        {
            g.FillRectangle(labelPaint, debugX - 3, debugY - 3, 6, 6);
            g.DrawString(debugStr, Font, labelPaint, Point.Empty);
        }


        private float sec2Pos(float sec)
        {
            return (sec - currentSecond) * pixelPerSecond;
        }

        private float pos2Sec(float pos)
        {
            return (pos / pixelPerSecond) + currentSecond;
        }

        public bool lockCursor = false;

        private bool rightDown = false;
        private bool middleDown = false;
        private bool leftDown = false;

        private int downX = 0;
        private int downY = 0;

        private int mouseX = 0, mouseY = 0;

        private int hoverSelection = 0; // 1 = selectionStart; 2 = selectionEnd;
        private int pressedSelection = 0;
        private float pressedCurrentSecond = 0;

        private void WaveformRenderControl_MouseDown(object sender, MouseEventArgs e)
        {
            if (waveData.Length == 0)
            {
                return;
            }
            if(leftDown || middleDown || rightDown) return;

            leftDown = e.Button == MouseButtons.Left;
            middleDown = e.Button == MouseButtons.Middle;
            rightDown = e.Button == MouseButtons.Right;
            downX = e.X;
            downY = e.Y;
            mouseX = PointToScreen(e.Location).X;
            mouseY = PointToScreen(e.Location).Y;
            if (rightDown)
            {
                if (lockCursor)
                {
                    Cursor.Hide();
                }
            }
            if (leftDown)
            {
                float clickingSecond = pos2Sec(e.X);
                bool outsideSelection = clickingSecond < selectionStart || clickingSecond > selectionEnd;

                if(hoverSelection != 0)
                {
                    pressedSelection = hoverSelection;
                    pressedCurrentSecond = currentSecond;
                }
                else if (!hasSelection || outsideSelection)
                {
                    hasNewSelection = true;
                    newSelectionOne = pos2Sec(e.X);
                    newSelectionTwo = pos2Sec(e.X);
                    update();
                }
                else if(hasSelection)
                {
                    if((DateTime.Now - lastClickSelectionTime).TotalMilliseconds > 1000)
                    {
                        lastClickSelectionTime = DateTime.Now;

                    }
                    else
                    {
                        lastClickSelectionTime = DateTime.MinValue;
                        SelectionClick?.Invoke(this, clickingSecond);
                    }
                }
                
            }

            if(middleDown)
            {
                float clickingSecond = pos2Sec(e.X);
                PreviewClick?.Invoke(this, clickingSecond);
            }
        }

        private DateTime lastClickSelectionTime = DateTime.MinValue;

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr SetCursorPos(int x, int y);

        private void WaveformRenderControl_MouseMove(object sender, MouseEventArgs e)
        {
            if (rightDown)
            {
                float deltaX = downX - e.X;
                if (lockCursor)
                {

                    SetCursorPos(mouseX, mouseY);
                }
                else
                {

                    downX = e.X;
                }
                float deltaSecond = deltaX / pixelPerSecond;
                currentSecond += deltaSecond;
                if (currentSecond < 0) { currentSecond = 0; }
                if(currentSecond > totalSecond - ((float)Width / (float)pixelPerSecond))
                {
                    currentSecond = totalSecond - ((float)Width / (float)pixelPerSecond);
                }
                update();
            }
            if(!leftDown && !middleDown && !rightDown)
            {
                if (hasSelection)
                {
                    float mouseAtSecond = pos2Sec(e.X);
                    float triggerSelectionStart = (selectionStart);
                    float triggerSelectionEnd = (selectionEnd);

                    float triggerWidth = 12f / pixelPerSecond;

                    float triggerStartBegin = triggerSelectionStart - triggerWidth / 2f;
                    float triggerStartEnd = selectionStart + (selectionEnd - selectionStart) / 2;
                    if(triggerSelectionEnd - triggerStartBegin > 0.3f)
                    {
                        triggerStartEnd = triggerStartBegin + 0.3f;
                    }
                    float triggerEndBegin = selectionStart + (selectionEnd - selectionStart) / 2; 
                    float triggerEndEnd = triggerSelectionEnd + triggerWidth / 2f;

                    if(triggerEndEnd - triggerEndBegin > 0.3f)
                    {
                        triggerEndBegin = triggerEndEnd - 0.3f;
                    }

                    //debugStr = mouseAtSecond + "";
                    //update();

                    if(triggerStartEnd >= selectionEnd)
                    {
                        triggerStartEnd = selectionStart;
                        triggerEndBegin = selectionEnd;
                    }

                    if(mouseAtSecond >= triggerStartBegin && mouseAtSecond <= triggerStartEnd)
                    {

                        Cursor = Cursors.VSplit;
                        hoverSelection = 1;

                    }
                    else if (mouseAtSecond >= triggerEndBegin && mouseAtSecond <= triggerEndEnd)
                    {

                        Cursor = Cursors.VSplit;
                        hoverSelection = 2;
                    }
                    else
                    {
                        Cursor = Cursors.Default;
                        hoverSelection = 0;
                    }

                }
                else
                {
                    Cursor = Cursors.Default;
                    hoverSelection = 0;
                }
            }

            if (leftDown && pressedSelection != 0) 
            {
                float deltaX = e.X - downX; ;
                downX = e.X;
                float deltaSecond = deltaX / pixelPerSecond + (currentSecond - pressedCurrentSecond);
                pressedCurrentSecond = currentSecond;
                if(pressedSelection == 1)
                {
                    selectionStart += deltaSecond;
                    if(selectionStart >= selectionEnd)
                    {
                        selectionStart = selectionEnd - 0.001f;
                    }
                    if(selectionStart < 0)
                    {
                        selectionStart = 0;
                    }
                }
                if (pressedSelection == 2)
                {
                    selectionEnd += deltaSecond;
                    if (selectionEnd <= selectionStart)
                    {
                        selectionEnd = selectionStart + 0.001f;
                    }
                    if(selectionEnd > totalSecond)
                    {
                        selectionEnd = totalSecond;
                    }
                }
                update();
            }
            else if(leftDown && hasNewSelection)
            {

                newSelectionTwo = pos2Sec(e.X);
                if(newSelectionTwo < 0) { newSelectionTwo = 0; }
                if(newSelectionTwo > totalSecond) { newSelectionTwo = totalSecond; }
                update();
            }
        }

        [Description("指示在右键拖动时，是否锁定光标以便能够一次性拖动更大范围")]
        public bool LockCursor
        {
            get
            {
                return lockCursor;
            }
            set { lockCursor = value; }
        }

        public float CurrentDisplayPosition { 
            get {
                return currentSecond;
            }  
            set { 
                currentSecond = value;
                update();
            }
        }

        public float TotalDisplayRange
        {
            get
            {
                return totalSecond - ((float)Width / pixelPerSecond);
            }
        }
        public float DisplayWidthRange
        {
            get
            {
                return ((float)Width / pixelPerSecond);
            }
        }

        public event EventHandler<EventArgs> SelectionChanged;

        private void WaveformRenderControl_MouseUp(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left) {
                
                leftDown = false;
                if (hasNewSelection)
                {
                    hasNewSelection  = false;
                    if(newSelectionOne != newSelectionTwo)
                    {
                        hasSelection = true;
                        selectionStart = newSelectionOne > newSelectionTwo ? newSelectionTwo : newSelectionOne;
                        selectionEnd = newSelectionOne < newSelectionTwo ? newSelectionTwo : newSelectionOne;
                        
                    }
                    else
                    {
                        hasSelection = false;
                    }
                    update();
                }
                pressedSelection = 0;
                SelectionChanged?.Invoke(this, EventArgs.Empty);
            }
            if (e.Button == MouseButtons.Middle) { middleDown = false; }
            if (e.Button == MouseButtons.Right) { 
                rightDown = false;
                if (lockCursor)
                {
                    Cursor.Show();
                }
            }

        }

        private void WaveformRenderControl_MouseWheel(object sender, MouseEventArgs e)
        {
            currentSecond -= (e.Delta / 120f);
            update();
            WaveformRenderControl_MouseMove(sender, e);
        }
    }
}
